package com.bestow.dox4j.component.handler;

import com.bestow.basic.domain.HlsDocFileParaTable;
import com.bestow.dox4j.Docx4jConstants;
import com.bestow.dox4j.IBookmarkHandler;
import com.bestow.dox4j.utils.Docx4jUtils;
import org.apache.commons.collections.CollectionUtils;
import org.docx4j.XmlUtils;
import org.docx4j.jaxb.XPathBinderAssociationIsPartialException;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.docx4j.wml.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import java.math.BigInteger;
import java.util.*;

/**
 * 生成横向表单的插件
 * Created by FJM on 2017/4/19.
 */
@Component
public class BlockBookmarkHandler implements IBookmarkHandler {

    private static Logger log = LoggerFactory.getLogger(BlockBookmarkHandler.class);

    @Override
    public String getType() {
        return Docx4jConstants.BOOKMARK_TYPE_FORMTABLE;
    }

    @Override
    public void process(WordprocessingMLPackage wordMLPackage, List<HlsDocFileParaTable> tableColumnList, List<HashMap> bookMarkValueList, String bookmark, Map<String, Object> params) throws Exception {
        replaceBlockWithBookmark(wordMLPackage, tableColumnList, bookMarkValueList);
    }

    public void replaceBlockWithBookmark(WordprocessingMLPackage wordMLPackage, List<HlsDocFileParaTable> bookMarkValueList, List<HashMap> tableResultList) throws Exception {
        if (CollectionUtils.isEmpty(bookMarkValueList) || CollectionUtils.isEmpty(tableResultList)) {
            log.warn("没有查出对应的值,本次横向表单书签替换结束。" );
            return;
        }
        replaceBlockBookmarkContents(wordMLPackage, bookMarkValueList, tableResultList);
        log.info("横向表单书签替换成功" );
    }

    private void replaceBlockBookmarkContents(WordprocessingMLPackage wordMLPackage, List<HlsDocFileParaTable> bookMarkValueList, List<HashMap> tableResultList)
            throws Exception {

        MainDocumentPart mainDocumentPart = wordMLPackage.getMainDocumentPart();
        // 1. 先通过书签拿到所有的P节点
        List<P> paras = getAllBookMarkParentP(mainDocumentPart, bookMarkValueList);
        if (paras == null || paras.size() < 1) {
            log.info("没有找到书签对应的P节点" );
            return;
        }
        // 2. 将文档中的包含P的标签先移除,并找到插入的下标，后续再添加带有值的P标签
        int insertIndex = removeSeriesPFromBody(mainDocumentPart, paras);
        if (insertIndex < 0) {
            return;
        }
        // 将compositeMap转换成list

        List<String> fontFamilyList = new ArrayList<>();
        List<String> fontSizeList = new ArrayList<>();
        List<String> underLineList = new ArrayList<>();
        List<String> boldList = new ArrayList<>();
        List<String> tableCols = new ArrayList<>();

        for (HlsDocFileParaTable map : bookMarkValueList) {
            String column_desc = map.getColumnDesc();
            tableCols.add(column_desc);
            //TODO 目前doc_file_para_table表中暂无以下4个字段，所以设置为空
            fontFamilyList.add(null);
            fontSizeList.add(null);
            underLineList.add(null);
            boldList.add(null);
        }
        List<List<String>> multiValues = new ArrayList<>();
        for (HashMap map : tableResultList) {
            List<String> values = new ArrayList<>();
            for (String title : tableCols) {
                String value = (map.get(title) != null ? map.get(title) : "" ).toString();
                values.add(value);
            }

            multiValues.add(values);
        }

        // 3. 遍历需要替换的值，生成一个最终替换的List<P>
        List<P> insertValues = new ArrayList<P>();
        for (List<String> values : multiValues) {
            List<P> ps = replaceSeriesBookmarkWithContent(paras, tableCols, values, fontFamilyList, fontSizeList, underLineList, boldList);
            insertValues.addAll(ps);
        }

        // 4. 将生成的List<P>插入到文档的指定位置

        mainDocumentPart.getContent().addAll(insertIndex, insertValues);

        // 5. 保存文档
    }

    /**
     * 删除连续的P标签，并返回第一个P的index
     *
     * @param doc
     * @param paras
     * @return
     */
    public int removeSeriesPFromBody(MainDocumentPart doc, List<P> paras) {
        Document wmlDocumentEl = doc.getJaxbElement();
        Body body = wmlDocumentEl.getBody();
        List<Object> content = body.getContent();
        int first = 0xffffffff >>> 1; // int 正数最大值
        for (P p : paras) {
            int index = content.indexOf(p);
            if (index < 0) {
                continue;
            }
            if (index < first) {
                first = index;
            }
            content.remove(index);
        }
        return first;
    }

    /**
     * 找到所有书签的父P 节点
     *
     * @param doc
     * @param bookmarkNames
     * @return 连续的p标签，包含了所有的书签在内
     * @throws XPathBinderAssociationIsPartialException
     * @throws JAXBException
     */
    public List<P> getAllBookMarkParentP(MainDocumentPart doc, List<HlsDocFileParaTable> bookmarkNames)
            throws XPathBinderAssociationIsPartialException, JAXBException {
        Iterator<HlsDocFileParaTable> iterator = bookmarkNames.iterator();
        List<P> result = new ArrayList<>();
        Document wmlDocumentEl = doc.getJaxbElement();
        Body body = wmlDocumentEl.getBody();
        List<Object> content = body.getContent();

        int first = 0xffffffff >>> 1; // 用于记录书签的第一个p标签的下标
        int last = -1; // 用来记录有书签的最后一个p标签

        while (iterator.hasNext()) {
            HlsDocFileParaTable next = iterator.next();
            String bookmark = next.getColumnName();
            String xpath = "//w:bookmarkStart[@w:name='" + bookmark + "']/..";
            List<Object> jaxbNodesViaXPath = doc.getJAXBNodesViaXPath(xpath, false);
            if (jaxbNodesViaXPath != null && jaxbNodesViaXPath.size() > 0) {
                Object o = jaxbNodesViaXPath.get(0);
                int index = content.indexOf(o);
                if (index > last) {
                    last = index;
                }
                if (index < first) {
                    first = index;
                }
            }
        }
        if (first > -1 && last >= first) {
            for (int i = first; i <= last; i++) {
                Object object = content.get(i);
                if (object instanceof P) {
                    result.add((P) object);
                }
            }
        }
        return result;
    }

    /**
     * 替换连续P标签中指定书签为指定值
     *
     * @param paras
     * @param bookmarkNames
     * @param bookmarkValues
     * @return
     */
    public List<P> replaceSeriesBookmarkWithContent(List<P> paras, List<String> bookmarkNames,
                                                    List<String> bookmarkValues, List<String> fontFamilyList,
                                                    List<String> fontSizeList, List<String> underlineList, List<String> boldList) {
        // 先复制一份，避免直接对paras修改，影响主体逻辑
        List<P> result = new ArrayList<>();
        for (P para : paras) {
            P deepCopy = XmlUtils.deepCopy(para);
            result.add(deepCopy);
        }

        for (P p : result) {
            List<Object> content = p.getContent();
            BigInteger lastId = null;
            ListIterator<Object> contentIter = content.listIterator();
            while (contentIter.hasNext()) {
                Object obj = contentIter.next();
                if (obj instanceof JAXBElement) { // 书签必然是JAXBElement类型
                    // 转换JAXBElement
                    Object unwrap = XmlUtils.unwrap(obj);
                    if (unwrap instanceof CTBookmark) { // 如果转换后是CTBookmark类型，则确认为书签开始标签
                        CTBookmark bookmark = (CTBookmark) unwrap;
                        lastId = bookmark.getId();
                        int valueIndex = bookmarkNames.indexOf(bookmark.getName());
                        // 判断该名称书签是否需要替换成value
                        if (valueIndex >= 0) {
                            // 先删除书签，生成R放到书签的下标
                            String fontFamily = fontFamilyList.get(valueIndex);
                            String fontSize = fontSizeList.get(valueIndex);
                            String underline = underlineList.get(valueIndex);
                            String bold = boldList.get(valueIndex);
                            R run = Docx4jUtils.makeRWithContent(bookmarkValues.get(valueIndex), fontFamily, fontSize, underline, bold);
                            int index = content.indexOf(obj); // 要使用obj作为查找对象而不是转换类型后的bookmark
                            if (index >= 0) {
                                contentIter.remove();
                                contentIter.add(run);
                            }
                        }
                    } else if (unwrap instanceof CTMarkupRange) { // 书签结束标签
                        CTMarkupRange bookmarkEnd = (CTMarkupRange) unwrap;
                        BigInteger id = bookmarkEnd.getId();
                        // 对应上面的书签开始标签
                        if (id != null && id.equals(lastId)) {
                            // 直接删除结束标签
                            contentIter.remove();
                        }
                    }
                }
            }
        }
        return result;
    }

}
